﻿Public Class ListBox

	Dim _IconSize As New Size(44, 44)
	Property IconSize As Drawing.Size
		Get
			Return _IconSize
		End Get
		Set(ByVal value As Drawing.Size)
			If value.IsEmpty Then Return
			_IconSize = value
			IconSpacing = New Size(value.Width / 4, value.Height / 4)
			Repaginate()
		End Set
	End Property

	Private IconSpacing As New Size(16, 16)
	Private Items As New SortedList(Of String, Item)
	Private Buttons As New List(Of Button)

	Dim osb As Bitmap

	Sub New()
		InitializeComponent()
		DoubleBuffered = True
		osb = New Bitmap("D:\Sony\DCIM\101MSDCF\DSC02911.jpg")
	End Sub

	Property Centered As Boolean

	Protected Overrides Sub OnSizeChanged(ByVal e As System.EventArgs)
		MyBase.OnSizeChanged(e)
		Repaginate()
		Rearrange()
	End Sub

	Dim ItemsX, ItemsY As Integer
	Dim PageIndicatorHeight As Integer = 32
	Dim ToolbarHeight As Integer = 64
	Dim ScrollingRect As Rectangle

	Private Sub Repaginate()
		Dim ys = If(Buttons.Count = 0, 0, ToolbarHeight)
		ItemsX = Math.Max(1, Int((Width - IconSpacing.Width) / (IconSize.Width + IconSpacing.Width)))
		ItemsY = Math.Max(1, Int((Height - ys - IconSpacing.Height - PageIndicatorHeight) / (IconSize.Height + IconSpacing.Height)))
		Dim o = (Width - (ItemsX * (IconSize.Width + IconSpacing.Width) + IconSpacing.Width)) / 2
		If Centered Then o = (Width - (Items.Count * (IconSize.Width + IconSpacing.Width) + IconSpacing.Width)) / 2
		For i = 0 To Items.Count - 1
			Dim Item = Items.Values(i)
			Item.Page = Int(i / (ItemsX * ItemsY))
			Dim y = Int((i Mod (ItemsX * ItemsY)) / ItemsX)
			Dim x = i Mod ItemsX
			Item.Rect = New Rectangle(o + Item.Page * Width + IconSpacing.Width + x * (IconSize.Width + IconSpacing.Width), ys + IconSpacing.Height + y * (IconSize.Height + IconSpacing.Height), IconSize.Width, IconSize.Height)
		Next
		If PageCount > 0 AndAlso cp > PageCount - 1 Then GoPage(PageCount - 1, True)
		Invalidate()
	End Sub

	Private Sub Rearrange()
		Dim m = 6
		Dim x1 = m
		For Each Button In Buttons
			Button.Rect = New Rectangle(x1, m, m * 8 + TextRenderer.MeasureText(Button.Text, Font).Width, ToolbarHeight - 2 * m)
			x1 += Button.Rect.Width + m
		Next
		ScrollingRect = New Rectangle(0, 0, Width, Height)
		If Buttons.Count > 0 Then
			ScrollingRect.Offset(0, ToolbarHeight)
			ScrollingRect.Height -= ToolbarHeight
			ScrollingRect.Height -= PageIndicatorHeight
		End If
	End Sub

	Dim cp = 0

	ReadOnly Property PageCount As Integer
		Get
			Return Math.Ceiling(Items.Count / (ItemsX * ItemsY))
		End Get
	End Property

	Event Tapped(ByVal Key As String)

	Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
		MyBase.OnPaint(e)
		e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
		If Buttons.Count > 0 Then
			Dim R As New Rectangle(0, 0, Width, ScrollingRect.Top)
			e.Graphics.FillRectangle(LGBv(R, Color.White, Color.LightGray), R)
			For y = 1 To 12
				Dim Pen As New Pen(Color.Black.Opacity(192 / (y ^ 2)))
				e.Graphics.DrawLine(Pen, 0, ToolbarHeight + y, Width, ToolbarHeight + y)
			Next
			For Each Button In Buttons
				R = Button.Rect
				Dim R2 = R
				R2.Inflate(1, 1)
				e.Graphics.FillRoundRect(R, LGBv(R2, Color.SlateGray, Color.DarkSlateGray), 12)
				R.Inflate(-1, -1)
				e.Graphics.FillRoundRect(R, LGBv(R2, Color.DarkSlateGray, Color.SlateGray), 10)
				e.Graphics.DrawText(Button.Text, Font, R, Color.White, "mc")
			Next
		End If
		e.Graphics.TranslateTransform(-LeftEdge, 0)
		Dim cr = ClientRectangle
		cr.Offset(LeftEdge, 0)
		Dim Args As New ListBoxDrawItemEventArgs
		Args.Graphics = e.Graphics
		For i = 0 To Items.Count - 1
			Args.Rect = Items.Values(i).Rect
			If cr.IntersectsWith(Args.Rect) Then
				Args.Item = Items.Values(i)
				RaiseEvent DrawItem(Me, Args)
			End If
		Next
		e.Graphics.ResetTransform()
		If PageCount > 1 Then
			Dim Rect = PageRect
			For i = 0 To PageCount - 1
				Dim R = New Rectangle(Rect.Left + Rect.Height * i / 2, Rect.Top + Rect.Height / 4, Rect.Height / 2, Rect.Height / 2)
				R.Inflate(-12, -12)
				e.Graphics.FillEllipse(Color.White.Opacity(If(cp = i, 255, 64)).Brush, R)
			Next
		End If
	End Sub

	ReadOnly Property PageRect As Rectangle
		Get
			Dim Rect = New Rectangle(0, ScrollingRect.Bottom, PageIndicatorHeight * (PageCount + 1) / 2, Height - ScrollingRect.Bottom)
			Rect.Offset((Width - Rect.Width) / 2, 0)
			Return Rect
		End Get
	End Property

	Event DrawItem(ByVal sender As Object, ByVal e As ListBoxDrawItemEventArgs)

	Class ListBoxDrawItemEventArgs : Inherits EventArgs
		Public Graphics As Graphics
		Public Rect As Rectangle
		Public Item As Item
	End Class

	Dim ClickPoint As Point
	Dim LastPoint As Point
	Dim LeftEdge As Integer
	Dim Destination As Integer

	Dim Watch As New Stopwatch
	Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
		MyBase.OnMouseDown(e)
		Watch.Reset()
		Watch.Start()
		ClickPoint = e.Location
		LastPoint = ClickPoint
	End Sub

	Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
		Static RI As Size
		MyBase.OnMouseMove(e)
		If e.Button = Windows.Forms.MouseButtons.None Then
			For Each Button In Buttons
				If Button.Rect.Contains(e.Location) Then
					Cursor = Cursors.Hand
					Return
				End If
				Cursor = Cursors.Default
			Next
		ElseIf e.Button = Windows.Forms.MouseButtons.Left Then
			If Not Dragging And Not Resizing Then
				If Math.Abs(e.X - ClickPoint.X) > 16 And Math.Abs(e.X - ClickPoint.X) / (Math.Abs(e.X - ClickPoint.X) + Math.Abs(e.Y - ClickPoint.Y)) > 0.7 Then Dragging = True
			End If
			If Not Resizing And Not Dragging Then
				If Math.Abs(e.Y - ClickPoint.Y) > 16 And Math.Abs(e.Y - ClickPoint.Y) / (Math.Abs(e.X - ClickPoint.X) + Math.Abs(e.Y - ClickPoint.Y)) > 0.7 Then Resizing = True : RI = IconSize
			End If
			If Dragging Then
				ScrollBy(e.X - LastPoint.X)
				LastPoint = e.Location
			End If
			If Resizing Then
				Dim yd = RI.Height + (e.Y - ClickPoint.Y)
				If yd < 44 Then yd = 44
				If yd > Height * 3 / 4 Then yd = Height * 3 / 4
				IconSize = New Size(yd, yd)
				Invalidate()
			End If
		End If
	End Sub

	Sub ScrollBy(ByVal dx As Integer)
		Handle.Scroll(dx, 0, ScrollingRect, ScrollingRect)
		LeftEdge -= dx
		Invalidate(New Rectangle(If(dx > 0, 0, Width - dx), ScrollingRect.Top, dx, ScrollingRect.Height))
	End Sub

	Dim _Dragging As Boolean
	Property Dragging As Boolean
		Get
			Return _Dragging
		End Get
		Set(ByVal value As Boolean)
			_Dragging = value
			If value Then Cursor = Cursors.Hand Else Cursor = Cursors.Default
		End Set
	End Property

   Dim _Resizing As Boolean
   Property Resizing As Boolean
      Get
         Return _Resizing
      End Get
      Set(ByVal value As Boolean)
         _Resizing = value
         If value Then Cursor = Cursors.SizeNS Else Cursor = Cursors.Default
      End Set
   End Property

   Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)
      MyBase.OnMouseUp(e)
      If Dragging Then
         Dragging = False
			PerformDrag(e.X - ClickPoint.X)
      ElseIf Resizing Then
         Resizing = False
      ElseIf Watch.ElapsedMilliseconds < 1000 Then
			Dim P = e.Location
			For Each Button In Buttons
				If Button.Rect.Contains(P) Then
					Button.Proc()
					Return
				End If
			Next
			P.Offset(cp * Width, 0)
			For Each Item In Items.Values.ToList
				If Item.Rect.Contains(P) And Item.Rect.Contains(ClickPoint) Then
					RaiseEvent Tapped(Item.Key)
					Return
				End If
			Next
		End If
   End Sub

	Protected Overrides Sub OnMouseWheel(ByVal e As System.Windows.Forms.MouseEventArgs)
		MyBase.OnMouseWheel(e)
		If e.Delta > 0 And cp > 0 Then
			GoPage(-1)
		ElseIf e.Delta < 0 And cp < PageCount - 1 Then
			GoPage(1)
		End If
		AnimatePage()
	End Sub

	Sub GoPage(ByVal dp As Integer, Optional ByVal Absolute As Boolean = False)
		If Absolute Then cp = dp Else cp += dp
		Destination = (cp) * Width
		AnimatePage()
		Invalidate(PageRect)
	End Sub

	Sub PerformDrag(ByVal dx As Integer)
		If dx > 0 And cp > 0 Then
			GoPage(-1)
		ElseIf dx < 0 And cp < PageCount - 1 Then
			GoPage(1)
		Else
			GoPage(0)
		End If
	End Sub

	Dim WithEvents Timer As New Timer With {.Interval = 25}

	Sub AnimatePage()
		Timer.Enabled = True
	End Sub

	Class Item
		Public Key As String
		Public Text As String
		Public Image As Image
		Public Page As Integer
		Public Rect As Rectangle
	End Class

	Sub Clear()
		Centered = False
		Items.Clear()
		Buttons.Clear()
		LeftEdge = -Width
		Destination = 0
		AnimatePage()
		Repaginate()
	End Sub

	Delegate Sub ButtonTap()

	Sub AddButton(ByVal Text As String, ByVal Tap As ButtonTap)
		Dim Button As New Button
		Button.Proc = Tap
		Button.Text = Text
		Buttons.Add(Button)
		If Buttons.Count = 1 Then
			Repaginate()
		End If
		Rearrange()
	End Sub

	Class Button
		Public Proc As ButtonTap
		Public Text As String
		Public Rect As Rectangle
	End Class

	Private Sub Timer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Timer.Tick
		Static Speed As Decimal = 0
		If LeftEdge = Destination Then
			Timer.Enabled = False
			Speed = 0
			Return
		End If
		Speed = -(LeftEdge - Destination) / 4
		If Speed > -1 And Speed < 0 Then Speed = -1
		If Speed < 1 And Speed > 0 Then Speed = 1
		ScrollBy(-Speed)
	End Sub

	Sub Redraw(ByVal File As String)
		If Items.ContainsKey(File) Then
			Dim R = Items(File).Rect
			R.Offset(-LeftEdge, 0)
			Invalidate(R)
		End If
	End Sub

	Sub AddItem(ByVal Key As String, ByVal Text As String, ByVal Image As Image)
		Dim Item As New Item
		Item.Key = Key
		Item.Text = Text
		Item.Image = Image
		Items.Add(Key, Item)
		Repaginate()
	End Sub

	Sub AddItem(ByVal Key As String, ByVal Text As String)
		AddItem(Key, Text, Nothing)
	End Sub

End Class
